/**
 * 
 */
package com.googlecode.lohr.parser.combinators.matcher;


import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;

import com.googlecode.lohr.parser.combinators.MatchError;
import com.googlecode.lohr.parser.combinators.node.AbstractNode;

public class ChoiceMatcher extends Matcher
{
	protected final ArrayList<Matcher> matchers= new ArrayList<Matcher>();
	
	public ChoiceMatcher(Matcher[] matchers)
	{
		for (Matcher matcher:matchers)
			this.matchers.add(matcher);
	}
	public ChoiceMatcher(Collection<Matcher> matchers)
	{
		for (Matcher matcher:matchers)
			this.matchers.add(matcher);
	}
	public ChoiceMatcher()
	{
	}

	@Override
	public void match(final MatchContext ctx, final int start) 
	{
		if (matchers.size() <= 0) {
			ctx.matchComplete(this, start, new MatchError("This choice contains no options", start));
			return;
		}
		
		if (matchers.size() <= 1) {
			new WrappedMatchTask(ctx, this, matchers.get(0), start).run();
			return;
		}
		
		final Matcher targetMatcher= this;
		final int targetStart= start;
		final AtomicInteger count= new AtomicInteger(matchers.size());
		for (Matcher matcher:matchers) {
			ctx.doMatch(matcher, start, new MatchListener() {
				@Override
				public void onMatchFound(final AbstractNode headNode) {
					ctx.matchFound(targetMatcher, targetStart, headNode);
				}
				
				@Override
				public void onMatchComplete(final MatchError headError) {
					if (count.decrementAndGet() <= 0) {
						
						// matches found, complete the choice with no error
						if (ctx.hasMatches(targetMatcher, targetStart)) {
							ctx.matchComplete(targetMatcher, targetStart);
							return;
						}

						// find last occurring error
						MatchError err= null;
						for (Matcher matcher:matchers) {
							MatchError e= ctx.getMatchError(matcher, targetStart);
							if (e != null) {
								if (err == null || err.position < e.position)
									err= e;
							}
						}
						
						// no error and no results, just complete the choice
						if (err == null) {
							ctx.matchComplete(targetMatcher, targetStart);
							return;
						}
						
						ctx.matchComplete(targetMatcher, targetStart, err);
					}
				}
			});
		}
	}

	public void addChoice(Matcher matcher) {
		this.matchers.add(matcher);
	}

	public void addChoices(Matcher... matchers) {
		for (Matcher matcher:matchers)
			this.matchers.add(matcher);
	}
}